﻿using System;
using System.Collections.Generic;
using System.Linq;
using Umbraco.Core.Models;
using Umbraco.Web;
using nForum.BusinessLogic.Models;
using umbraco.BusinessLogic;
using umbraco.NodeFactory;
using umbraco.cms.businesslogic.web;

namespace nForum.BusinessLogic.Data
{
    public class ForumFactory
    {
        /// <summary>
        /// Constants
        /// </summary>
        public const string ExamineSearcher = "nForumEntrySearcher";
        public const bool UseNodeFactoryForAllQueries = false;
        public readonly int _rootForumId;
        public IPublishedContent _rootForumNode;    

        // Store the root forum node for use in nodefactory lookups
        //public readonly int _rootForumNode = Helpers.MainForumSettings().Id;

        /// <summary>
        /// Global objects
        /// </summary>
        public NodeMapper Mapper = new NodeMapper();        
        //var query = criteria.Id(parentnode).And().Field("bodyText", "is awesome".Escape()).Or().Field("bodyText", "rock".Fuzzy());

        //Constructor
        public ForumFactory(int? overideRoot = null)
        {
            _rootForumNode = UmbracoAppHelpers.GetForumRootNode(overideRoot);
            _rootForumId = _rootForumNode != null ? _rootForumNode.Id : -1;
            //_rootForumId = ReturnRootForumId();
        }


        #region Category Methods



        /// <summary>
        /// Returns a list of all sub categories within a parent category
        /// </summary>
        /// <param name="categoryNodeId"></param>
        /// <returns></returns>
        public IEnumerable<ForumCategory> ReturnAllSubCategoriesInCategory(int categoryNodeId)
        {
            var subCategories = UmbracoAppHelpers.GetPage(categoryNodeId).Children.Where(x => x.DocumentTypeAlias == AppConstants.NodeTypeCategory).OrderBy(x => x.SortOrder);
            return subCategories.Select(searchResult => Mapper.MapForumCategory(searchResult));
        }

        /// <summary>
        /// Returns all the current categories on the site
        /// </summary>
        /// <returns></returns>
        public IEnumerable<ForumCategory>ReturnAllCategories()
        {
            var cats = _rootForumNode.Descendants(AppConstants.NodeTypeCategory).OrderBy(x => x.SortOrder);
            return cats.Select(searchResult => Mapper.MapForumCategory(searchResult));
        }

        #endregion

        #region Topic Methods

        /// <summary>
        /// Returns a list of topics within a category
        /// </summary>
        /// <param name="categoryNodeId"></param>
        /// <param name="sortByLastPostDate"></param>
        /// <returns></returns>
        public IEnumerable<ForumTopic> ReturnAllTopicsInCategory(int categoryNodeId, bool sortByLastPostDate = false)
        {
            var category = UmbracoAppHelpers.GetPage(categoryNodeId);
            var topics = category.Descendants(AppConstants.NodeTypeTopic)
                                .Where(x => x.GetPropertyValue<string>("forumTopicParentCategoryID") == categoryNodeId.ToString())
                                .OrderByDescending(x => x.CreateDate);
            if(sortByLastPostDate)
            {
                topics = topics.OrderByDescending(x => Convert.ToDateTime(x.GetProperty("forumTopicLastPostDate").Value));
            }
            return topics.Select(searchResult => Mapper.MapForumTopic(searchResult));
        }

        /// <summary>
        /// Returns the count of all topics within this category
        /// </summary>
        /// <param name="categoryNodeId"></param>
        /// <returns></returns>
        public int ReturnAllTopicsInCategoryCount(int categoryNodeId)
        {
            var category = UmbracoAppHelpers.GetPage(categoryNodeId);
            return category.Descendants(AppConstants.NodeTypeTopic).Count(x => x.GetPropertyValue<string>("forumTopicParentCategoryID") == categoryNodeId.ToString());
        }
        
        /// <summary>
        /// Returns all forum topics
        /// </summary>
        /// <param name="sortByLastPostDate"></param>
        /// <returns></returns>
        public IEnumerable<ForumTopic> ReturnAllTopics(bool sortByLastPostDate)
        {
            var topics = _rootForumNode.Descendants(AppConstants.NodeTypeTopic)
                                            .OrderByDescending(x => x.CreateDate);
            if (sortByLastPostDate)
            {
                topics = topics.OrderByDescending(x => Convert.ToDateTime(x.GetProperty("forumTopicLastPostDate").Value));
            }
            return topics.Select(searchResult => Mapper.MapForumTopic(searchResult));
        }

        /// <summary>
        /// Returns a specified amount of active topics
        /// </summary>
        /// <param name="amountToTake"></param>

        /// <returns></returns>
        public IEnumerable<ForumTopic> ReturnActiveTopics(int amountToTake)
        {
            var topics = _rootForumNode.Descendants(AppConstants.NodeTypeTopic)
                              .OrderByDescending(x => x.GetProperty("forumTopicLastPostDate").Value.ToDateTime());

            return topics.Take(amountToTake).Select(searchResult => Mapper.MapForumTopic(searchResult));
        }

        /// <summary>
        /// Returns the most recent active topic in a category (Most recent to have a post in)
        /// </summary>
        /// <param name="categoryNodeId"></param>
        /// <returns></returns>
        public ForumTopic ReturnMostRecentActiveTopicInCategory(int categoryNodeId)
        {
            return ReturnAllTopicsInCategory(categoryNodeId, true).FirstOrDefault();
        }

        /// <summary>
        /// This updates a topic with the current latest post date
        /// </summary>
        /// <param name="topicId"></param>
        public void UpdateTopicWithLastPostDate(int topicId)
        {
            // This is a bit shite, but its the best way to keep performance on some of the larger select queries.
            // Especially if you have 10000's of nodes
            // Get the topic to update and get generic user
            var u = new User(0);
            var topic = new Document(topicId);

            // Now get the latest post in this topic, we do this with the node API to make 100%
            // sure that its correct as Examine takes a few seconds to update
            var postList = new Node(topicId).ChildrenAsList;
            var lastPost = (from p in postList
                            orderby p.CreateDate descending
                            select p).FirstOrDefault();

            // Update the topic
            if(lastPost != null)
            {
                topic.getProperty("forumTopicLastPostDate").Value = lastPost.CreateDate;
            }
            else
            {
                topic.getProperty("forumTopicLastPostDate").Value = null;
            }

            // Finally save it all
            topic.sortOrder = 0;

            // publish application node
            topic.Publish(u);

            // update the document cache so its available in the XML
            umbraco.library.UpdateDocumentCache(topicId);
        }

        #endregion

        #region Post Methods

        /// <summary>
        /// Returns a list of posts from a parent topic
        /// </summary>
        /// <param name="topicNodeId"></param>
        /// <returns></returns>
        public IEnumerable<ForumPost> ReturnAllPostsInTopic(int topicNodeId)
        {
            var posts = UmbracoAppHelpers.GetPage(topicNodeId).Children.Where(x => x.DocumentTypeAlias == AppConstants.NodeTypePost).OrderBy(x => x.CreateDate);
            return posts.Select(searchResult => Mapper.MapForumPost(searchResult));
        }


        /// <summary>
        /// Returns the sum of all votes for all posts within a topic 
        /// </summary>
        /// <param name="topicNodeId"></param>
        /// <returns></returns>
        public int ReturnSumOfVotesInTopic(int topicNodeId)
        {
            var posts = UmbracoAppHelpers.GetPage(topicNodeId).Children.Where(x => x.DocumentTypeAlias == AppConstants.NodeTypePost);
            return posts.Select(searchResult => searchResult.GetProperty("forumPostKarma").Value.ToInt32()).Sum();
        }

        /// <summary>
        /// Returns the count of posts within a topic
        /// </summary>
        /// <param name="topicNodeId"></param>
        /// <returns></returns>
        public int ReturnPostCountInTopic(int topicNodeId)
        {
            var posts = UmbracoAppHelpers.GetPage(topicNodeId).Children.Where(x => x.DocumentTypeAlias == AppConstants.NodeTypePost);
            return posts.Count();
        }

        /// <summary>
        /// Return a list of posts by a specific member
        /// </summary>
        /// <param name="memberId"></param>
        /// <returns></returns>
        public IEnumerable<ForumPost> ReturnAllPostsByMemberId(int memberId)
        {
            var posts = _rootForumNode.Descendants(AppConstants.NodeTypePost)
                              .Where(x => x.GetPropertyValue<string>("forumPostOwnedBy") == memberId.ToString())
                              .OrderByDescending(x => x.CreateDate);

            return posts.Select(searchResult => Mapper.MapForumPost(searchResult));
        }


        /// <summary>
        /// Get a specifed amount of posts ordered by negative karma
        /// </summary>
        /// <param name="amounttotake"></param>
        /// <returns></returns>
        public IEnumerable<ForumPost> ReturnAllBadPosts(int amounttotake)
        {
            // This is a rather heavy query, need to rethink it
            var rootNode2 = new Node(-1);
            var firstChildNode = rootNode2.ChildrenAsList.FirstOrDefault();
            if (firstChildNode != null)
            {
                var rootNode = UmbracoAppHelpers.GetPage(firstChildNode.Id);
                var posts = rootNode.Descendants(AppConstants.NodeTypePost).OrderBy(x => x.GetProperty("forumPostKarma").Value.ToInt32()).Take(amounttotake);
                return posts.Select(searchResult => Mapper.MapForumPost(searchResult));   
            }
            return new List<ForumPost>();
        }

        /// <summary>
        /// Returns a list of posts based upon a search term
        /// </summary>
        /// <param name="searchTerm"></param>
        /// <returns></returns>
        public IEnumerable<ForumPost> SearchPosts(string searchTerm)
        {
            var posts =
                _rootForumNode.Descendants(AppConstants.NodeTypePost)
                              .Where(x => x.Name.ToLower().Contains(searchTerm) || x.GetPropertyValue<string>("forumPostContent").ToLower().Contains(searchTerm));
            return posts.Select(searchResult => Mapper.MapForumPost(searchResult));
        }


        /// <summary>
        /// Returns the latest post in a topic
        /// </summary>
        /// <param name="topicNodeId"></param>
        /// <returns></returns>
        public ForumPost ReturnLatestPostInTopic(int topicNodeId)
        {
            var posts = UmbracoAppHelpers.GetPage(topicNodeId).Children.Where(x => x.DocumentTypeAlias == AppConstants.NodeTypePost).OrderByDescending(x => x.CreateDate);
            return Mapper.MapForumPost(posts.FirstOrDefault());
        }


        /// <summary>
        /// Returns the post that started the topic
        /// </summary>
        /// <param name="topicNodeId"></param>
        /// <returns></returns>
        public ForumPost ReturnTopicStarterPost(int topicNodeId)
        {
            var posts = UmbracoAppHelpers.GetPage(topicNodeId).Children.Where(x => x.DocumentTypeAlias == AppConstants.NodeTypePost && x.GetPropertyValue<bool>("forumPostIsTopicStarter"));
            return Mapper.MapForumPost(posts.FirstOrDefault());
        }

        #endregion

    }
}
